package vip.liux.front.domain.models.payment;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import okhttp3.*;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMethod;
import vip.liux.core.exception.SystemException;
import vip.liux.core.utils.JsonUtils;
import vip.liux.front.domain.shared.payment.PayGateway;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static software.amazon.awssdk.utils.http.SdkHttpUtils.urlEncode;

public class WechatAppletPaymentGateway implements IPaymentGateway {

    private static final Logger log = LoggerFactory.getLogger(WechatAppletPaymentGateway.class);

    @Override
    public boolean isValid(PaymentRequest paymentRequest, Map<String, Object> properties) {
        Assert.notNull(paymentRequest.getId(), "paymentRequest.id must not be null or empty");
        String url = QUERY_ORDER.replace("{out_trade_no}", paymentRequest.getId()) + "?mchid=" + MCHID;

        WechatResponse response = getRequest(url, "");

        if (response.status() == 200 && verifySignature(response.signature(), response.body(), response.nonceStr(), response.timestamp())) {
            log.info("微信小程序支付订单查询结果:{}", response.body());
            try {
                JsonNode jsonNode = JsonUtils.getObjectMapper().readTree(response.body());
                if (jsonNode.get("trade_state").asText().equals("SUCCESS")) {
                    log.debug("微信小程序支付订单查询支付成功:{}", jsonNode);
                    return true;
                }
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }

    /**
     * Returns if a plugin should be invoked according to the given delimiter.
     *
     * @param delimiter must not be {@literal null}.
     * @return if the plugin should be invoked
     */
    @Override
    public boolean supports(@NotNull PayGateway delimiter) {
        return PayGateway.WECHAT_APPLET.equals(delimiter);
    }

    private static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version");

    private static final String VERSION = System.getProperty("java.version");

    private static final String KEY_ALGORITHM = "SHA256WithRSA";

    // 认证类型，目前为WECHATPAY2-SHA256-RSA2048
    private static final String AUTH_TYPE = "WECHATPAY2-SHA256-RSA2048";

    // 微信支付平台域名
    private static final String DOMAIN = "https://api.mch.weixin.qq.com";

    public static final String QUERY_ORDER = "/v3/pay/transactions/out-trade-no/{out_trade_no}";

    // 商户 API 公钥证书序列号
    private static final String SERIAL_NUMBER = "";

    // 商户号
    private static final String MCHID = "";

    private static final PrivateKey PRIVATE_KEY = null;

    private static final PublicKey PUBLIC_KEY = null;

    public static final OkHttpClient client = new OkHttpClient.Builder()
            .readTimeout(10000L, TimeUnit.MILLISECONDS)
            .connectTimeout(10000L, TimeUnit.MILLISECONDS)
            .writeTimeout(10000L, TimeUnit.MILLISECONDS)
            .connectionPool(new ConnectionPool(5, 5L, TimeUnit.MINUTES))
            .build();

    public WechatResponse getRequest(String url, String jsonData) {

        String authorization = buildAuthorization(RequestMethod.GET, url, MCHID, SERIAL_NUMBER, jsonData);

        // 创建请求体
        RequestBody body = RequestBody.create(jsonData, MediaType.get("application/json; charset=utf-8"));

        Headers headers = Headers.of(getHeaders(authorization, SERIAL_NUMBER));

        // 创建请求
        Request request = new Request.Builder()
                .url(DOMAIN.concat(url))
                .headers(headers)
                .post(body)
                .build();
        return execution(request);
    }

    public WechatResponse postRequest(String url, String jsonData) {

        String authorization = buildAuthorization(RequestMethod.POST, url, MCHID, SERIAL_NUMBER, jsonData);

        // 创建请求体
        RequestBody body = RequestBody.create(jsonData, MediaType.get("application/json; charset=utf-8"));

        Headers headers = Headers.of(getHeaders(authorization, SERIAL_NUMBER));

        // 创建请求
        Request request = new Request.Builder()
                .url(DOMAIN.concat(url))
                .headers(headers)
                .post(body)
                .build();
        return execution(request);
    }

    /**
     * 验证签名
     *
     * @param signature 待验证的签名
     * @param body      应答主体
     * @param nonce     随机串
     * @param timestamp 时间戳
     * @return 签名结果
     */
    public static boolean verifySignature(String signature, String body, String nonce, String timestamp) {
        return checkByPublicKey(buildSignMessage(timestamp, nonce, body), signature);
    }

    private Map<String, String> getBaseHeaders(String authorization) {
        String userAgent = String.format(
                "WeChatPay-PaymentGateway-HttpClient/%s (%s) Java/%s",
                getClass().getPackage().getImplementationVersion(),
                OS,
                VERSION == null ? "Unknown" : VERSION);

        Map<String, String> headers = new HashMap<>(3);
        headers.put("Accept", "application/json");
        headers.put("Authorization", authorization);
        headers.put("User-Agent", userAgent);
        return headers;
    }

    private Map<String, String> getHeaders(String authorization, String serialNumber) {
        Map<String, String> headers = getBaseHeaders(authorization);
        headers.put("Content-Type", "application/json");
        if (StringUtils.isNotEmpty(serialNumber)) {
            headers.put("Wechatpay-Serial", serialNumber);
        }
        return headers;
    }

    private Map<String, String> getUploadHeaders(String authorization, String serialNumber) {
        Map<String, String> headers = getBaseHeaders(authorization);
        headers.put("Content-Type", "multipart/form-data;boundary=\"boundary\"");
        if (StringUtils.isNotEmpty(serialNumber)) {
            headers.put("Wechatpay-Serial", serialNumber);
        }
        return headers;
    }

    /**
     * 构建 v3 接口所需的 Authorization
     *
     * @param method       {@link RequestMethod} 请求方法
     * @param urlSuffix    可通过 WxApiType 来获取，URL挂载参数需要自行拼接
     * @param mchId        商户Id
     * @param serialNumber 商户 API 公钥证书序列号
     * @param body         接口请求参数
     * @return {@link String} 返回 v3 所需的 Authorization
     */
    public static String buildAuthorization(
            RequestMethod method, String urlSuffix, String mchId,
            String serialNumber, String body) {

        String nonceStr = RandomStringUtils.random(20); // 随机字符库
        long timestamp = System.currentTimeMillis(); // 当前时间戳

        // 构建签名参数
        String buildSignMessage = buildSignMessage(method, urlSuffix, timestamp, nonceStr, body);
        // 生成签名
        String signature = encryptByPrivateKey(buildSignMessage);
        // 根据平台规则生成请求头 authorization
        return getAuthorization(mchId, serialNumber, nonceStr, String.valueOf(timestamp), signature);
    }


    /**
     * 公钥验证签名
     *
     * @param data    需要加密的数据
     * @param rwaSign 签名
     * @return 验证结果
     */
    public static boolean checkByPublicKey(String data, String rwaSign) {
        try {
            Signature signature = Signature.getInstance(KEY_ALGORITHM);
            signature.initVerify(PUBLIC_KEY);
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            return signature.verify(Base64.getDecoder().decode(rwaSign.getBytes(StandardCharsets.UTF_8)));
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            log.warn("签名失败:{}", e.getMessage());
            throw new SystemException(e);
        }
    }

    /**
     * 私钥签名
     *
     * @param data 需要加密的数据
     * @return 加密后的数据
     */
    public static String encryptByPrivateKey(String data) {
        try {
            Signature signature = Signature.getInstance(KEY_ALGORITHM);
            signature.initSign(PRIVATE_KEY);
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            byte[] signed = signature.sign();
            return Base64.getEncoder().encodeToString(signed);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            log.warn("签名失败:{}", e.getMessage());
            throw new SystemException(e);
        }
    }

    /**
     * 获取授权认证信息
     *
     * @param mchId        商户号
     * @param serialNumber 商户API公钥证书序列号
     * @param nonceStr     请求随机串
     * @param timestamp    时间戳
     * @param signature    签名值
     * @return 请求头 Authorization
     */
    public static String getAuthorization(String mchId, String serialNumber, String nonceStr, String timestamp, String signature) {
        Map<String, String> params = new HashMap<>(5);
        params.put("mchid", mchId);
        params.put("serial_no", serialNumber);
        params.put("nonce_str", nonceStr);
        params.put("timestamp", timestamp);
        params.put("signature", signature);
        return AUTH_TYPE.concat(" ").concat(createLinkString(params, ",", false, true));
    }

    public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            // 拼接时，不包括最后一个&字符
            if (i == keys.size() - 1) {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value);
                }
            } else {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
                }
            }
        }
        return content.toString();
    }

    /**
     * 构造签名串
     *
     * @param timestamp 应答时间戳
     * @param nonceStr  应答随机串
     * @param body      应答报文主体
     * @return 应答待签名字符串
     */
    public static String buildSignMessage(String timestamp, String nonceStr, String body) {
        return buildSignMessage(List.of(timestamp, nonceStr, body));
    }

    /**
     * 构造签名串
     *
     * @param signMessage 待签名的参数
     * @return 构造后带待签名串
     */
    public static String buildSignMessage(List<String> signMessage) {
        if (signMessage == null || signMessage.isEmpty()) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }


    /**
     * 构造签名串
     *
     * @param method    {@link RequestMethod} GET,POST,PUT等
     * @param url       请求接口 /v3/certificates
     * @param timestamp 获取发起请求时的系统当前时间戳
     * @param nonceStr  随机字符串
     * @param body      请求报文主体
     * @return 待签名字符串
     */
    public static String buildSignMessage(RequestMethod method, String url, long timestamp, String nonceStr, String body) {
        return buildSignMessage(List.of(method.name(), url, String.valueOf(timestamp), nonceStr, body));
    }

    /**
     * 获取证书
     *
     * @param inputStream 证书文件
     * @return {@link X509Certificate} 获取证书
     */
    public static X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }

    public WechatResponse execution(Request request) {

        // 发送请求并获取响应
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }
            String timestamp = response.header("Wechatpay-Timestamp");
            String nonceStr = response.header("Wechatpay-Nonce");
            String serialNo = response.header("Wechatpay-Serial");
            String signature = response.header("Wechatpay-Signature");
            Assert.notNull(response.body(), "response body must not be null");
            String bodyString = response.body().string();
            int status = response.code();
            return new WechatResponse(timestamp, nonceStr, serialNo, signature, bodyString, status);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public record WechatResponse(String timestamp, String nonceStr, String serialNumber, String signature, String body,
                                 int status) {
    }


    public static void main(String[] args) {
        // 获取证书序列号
        X509Certificate certificate = getCertificate(InputStream.nullInputStream());

        System.out.println("输出证书信息:\n" + certificate);
        System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
        System.out.println("版本号:" + certificate.getVersion());
        System.out.println("签发者：" + certificate.getIssuerX500Principal());
        System.out.println("有效起始日期：" + certificate.getNotBefore());
        System.out.println("有效终止日期：" + certificate.getNotAfter());
        System.out.println("主体名：" + certificate.getSubjectX500Principal());
        System.out.println("签名算法：" + certificate.getSigAlgName());
        System.out.println("签名：" + Arrays.toString(certificate.getSignature()));
    }
}
